/*
 * Copyright (c) 2022. China Mobile (SuZhou) Software Technology Co.,Ltd. All rights reserved.
 * Lakehouse is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

package com.chinamobile.cmss.lakehouse.common.utils;

import static org.apache.commons.lang3.StringUtils.isBlank;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Actions {
    private List<Action> actions = new LinkedList<>();

    private Actions() {

    }

    public static Actions newBuilder() {
        return new Actions();
    }

    public Actions addAction(Action action) {
        action.verifyAction();
        this.actions.add(action);
        return this;
    }

    public int numActions() {
        return actions.size();
    }

    public void run() {
        Iterator<Action> it = this.actions.iterator();
        while (it.hasNext()) {
            Action action = it.next();

            boolean success;
            try {
                success = runAction(action);
            } catch (Exception e) {
                log.error("Uncaught exception thrown when running action [ {} ]:", action.getActionName(),
                    e);
                success = false;

            }
            if (action.getContinueOn() != null && success == action.getContinueOn()) {
                continue;
            } else {
                // terminate
                break;
            }
        }
    }

    private boolean runAction(Action action) throws InterruptedException {
        for (int i = 0; i < action.getNumRetries(); i++) {

            ActionResult actionResult = action.getSupplier().get();

            if (actionResult.isSuccess()) {
                log.info("Successfully completed action [ {} ]", action.getActionName());
                if (action.getOnSuccess() != null) {
                    action.getOnSuccess().accept(actionResult);
                }
                return true;
            } else {
                if (actionResult.getErrorMsg() != null) {
                    log.warn("Not completing action [ {} ] :- {} - [ATTEMPT] {}/{}", action.getActionName(),
                        actionResult.getErrorMsg(), i + 1, action.getNumRetries());
                } else {
                    log.warn("Not completing action [ {} ] [ATTEMPT] {}/{}", action.getActionName(), i + 1,
                        action.getNumRetries());
                }

                Thread.sleep(action.sleepBetweenInvocationsMs);
            }
        }
        log.error("Failed completing action [ {} ]. Giving up!", action.getActionName());
        if (action.getOnFail() != null) {
            action.getOnFail().accept(action.getSupplier().get());
        }
        return false;
    }

    @Data
    @Builder(toBuilder = true)
    public static class Action {
        private String actionName;
        private int numRetries = 1;
        private Supplier<ActionResult> supplier;
        private long sleepBetweenInvocationsMs = 500;
        private Boolean continueOn;
        private Consumer<ActionResult> onFail;
        private Consumer<ActionResult> onSuccess;

        public void verifyAction() {
            if (isBlank(actionName)) {
                throw new RuntimeException("Action name is empty!");
            }
            if (supplier == null) {
                throw new RuntimeException("Supplier is not specified!");
            }
        }
    }

    @Data
    @Builder
    public static class ActionResult {
        private boolean success;
        private String errorMsg;
        private Object result;
        private Throwable exception;
    }
}
